#include "request.hpp"

#include "net_common.hpp"

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
//#include <boost/enable_shared_from_this.hpp>

#include <vector>

class tcp_client_t {// : public boost::enable_shared_from_this<tcp_client_t> {
public:
  boost::signals2::signal<void(bool)> connected_;
  boost::signals2::signal<void(const std::string &)> disconnected_;
  boost::signals2::signal<void(const char *, size_t)> on_data_;

  tcp_client_t(
      boost::asio::io_service& svc
    , const boost::asio::ip::address& ip_address
    , const short port
    ) 
    : is_connected_(false)
    , endpoint_(ip_address, port)
    , socket_(svc)
    , in_buf_(buffers_size)
  {
  }

  void async_connect() {
    socket_.async_connect(endpoint_, boost::bind(&tcp_client_t::internal_connected_handler, this/*shared_from_this()*/, _1));
  }

  void disconnect(const std::string &reason) {
    is_connected_ = false;
    boost::system::error_code ec;
    socket_.shutdown(boost::asio::socket_base::shutdown_both, ec);
    socket_.close(ec);
  }

  bool is_connected() {
    return is_connected_;
  }

  void send(req_t request) {
    socket_.async_send(boost::asio::buffer(&request.buf()[0],request.buf().size()), boost::bind(&tcp_client_t::write_handler, this/*shared_from_this()*/, request, _1, _2));
  }

private:

  void internal_connected_handler(const boost::system::error_code& error) {
    if (!error) {
      is_connected_ = true;
      connected_(true);
      socket_.async_receive(boost::asio::buffer(&in_buf_[0],in_buf_.size()), boost::bind(&tcp_client_t::read_handler, this/*shared_from_this()*/, _1, _2));
    }
    else {
      is_connected_ = false;
      connected_(false);
    }
  }
  
  void write_handler(req_t request, const boost::system::error_code& error, std::size_t bytes_transferred) {
    if (error) {
      disconnect(error.message());
    }
    if (request.buf().size() != bytes_transferred) {
      disconnect("not all data transfered");
    }
  }

  void read_handler(const boost::system::error_code& error, std::size_t bytes_transferred) {
    if (error) {
      disconnect(error.message());
      return;
    }
    if (bytes_transferred == in_buf_.size()) {
      disconnect("input buffer overflow");
      return;
    }
    on_data_(&in_buf_[0], bytes_transferred);

    socket_.async_receive(boost::asio::buffer(&in_buf_[0],in_buf_.size()), boost::bind(&tcp_client_t::read_handler, this/*shared_from_this()*/, _1, _2));
  }

  bool is_connected_;
  boost::asio::ip::tcp::endpoint endpoint_;
  boost::asio::ip::tcp::socket socket_;
  std::vector<char> in_buf_;
};
